package com.hup.minicontroller.common.bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.hup.minicontroller.common.bluetooth.framework.BtChannel;
import com.hup.minicontroller.common.bluetooth.framework.BtChannelHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtDataHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtDataHandler.DataMethodInvoker;
import com.hup.minicontroller.common.bluetooth.handler.BtDataHandlerRegister;
import com.hup.minicontroller.common.bluetooth.handler.BtHeartbeatHandler.BtIdleCallback;
import com.hup.minicontroller.common.bluetooth.handler.BtLoginHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtLoginHandler.BtLoginCallback;
import com.hup.minicontroller.common.bluetooth.model.BaseBtDataPacket;
import com.hup.minicontroller.common.thread.component.EfficientExecutor;
import com.hup.minicontroller.common.util.Supplier;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import lombok.Getter;
import lombok.Setter;


/**
 * BluetoothServerSocket服务端;<pre>
 * 注意:服务端的线程池在
 * .{@link #stopServer()}时就会完全关闭
 * .在 {@link #startServer()}时会根据配置重新构建,因为服务端一般很少频繁启动
 * 权限:
 * <uses-permission android:name="android.permission.BLUETOOTH" />
 * 资料:
 * https://developer.android.google.cn/guide/topics/connectivity/bluetooth
 * @author hugan
 * @date 2021/5/14
 */
public class BluetoothServer {
    private static final String TAG = BluetoothServer.class.getSimpleName();

    private final BtServerConfig serverConfig;
    private final StartedObj startedObj;
    private final MyClientHandler clientHandlerInstance;
    private final BtLoginHandler btLoginHandlerInstance;

    public BluetoothServer(BtServerConfig serverConfig) {
        this.serverConfig = serverConfig;
        startedObj = new StartedObj();
        startedObj.dataHandlerMap = serverConfig.registerDataHandlerMap();
        clientHandlerInstance = new MyClientHandler();
        if (serverConfig.loginCallback != null) {
            btLoginHandlerInstance = new BtLoginHandler(serverConfig.loginCallback);
        } else {
            btLoginHandlerInstance = null;
        }
    }

    /**
     * 服务端关闭时的处理
     *
     * @param isAtStart true-启动时调用本方法;false-启动后(接收客户端时)调用本方法
     */
    private void onSocketStopped(boolean isAtStart) {
        startedObj.startState = StartState.STOPPED;
        startedObj.releaseExecutor();
        startedObj.serverSocket = BtUtil.closeSilently(startedObj.serverSocket);
        if (isAtStart) {
            serverConfig.serverCallback.onServerStartResult(false);
        } else {
            serverConfig.serverCallback.onServerStopped();
        }
    }

    /**
     * 启动服务端, 启动配置为{@link BtServerConfig}
     */
    public void startServer() {
        startedObj.assertState(StartState.STOPPED);
        Log.i(TAG, "startServer: 正在启动服务端");
        startedObj.startState = StartState.STARTING;

        startedObj.buildExecutor(serverConfig);
        startedObj.executor.execute(() -> {
            try {
                startedObj.serverSocket = serverConfig.bluetoothAdapter.listenUsingRfcommWithServiceRecord(serverConfig.serverName, serverConfig.uuid);
                serverConfig.serverCallback.onServerStartResult(true);
            } catch (IOException e) {
                //一般是蓝牙没开
                Log.w(TAG, "startServer: 服务器启动异常:" + e);
                onSocketStopped(true);
                return;
            }
            startedObj.startState = StartState.STARTED;
            try {
                //noinspection InfiniteLoopStatement
                while (true) {//只能写true,serverSocket又没提供isClose方法
                    /*
                      实际情况:(应该是几年前的设备是这样) todo 验证
                      .一个蓝牙UUID只能同时accept()一个client
                      .在第二个client请求连接时:server端代码无反应,client会返回连接错误
                     */
                    BluetoothSocket client = startedObj.serverSocket.accept();
                    //直接在新线程中处理client:初始化,接收数据等
                    BtChannel btChannel = BtChannel.serverChannel(client, clientHandlerInstance,
                            startedObj.dataHandlerMap,
                            btLoginHandlerInstance,
                            serverConfig.idleCallback);
                    startedObj.executor.execute(btChannel);
                    //此处不应像demo那样关闭server,要用于"当前client断开后,继续连接下一个client"
                }
            } catch (IOException e) {
                //一般是主动stopServer,或用户关闭设备的蓝牙时达到
                //Log.i(TAG, "startServer:accept异常", e);
                onSocketStopped(false);
            } catch (Exception e) {
                //一般不会发生??除非是executor被reject了
                serverConfig.serverCallback.serverExceptionCaught(null, e);
                onSocketStopped(false);
            }
        });
    }

    /**
     * 关闭服务端
     */
    public void stopServer() {
        startedObj.assertState(StartState.STARTED);
        Log.i(TAG, "stopServer: 正在关闭服务端");
        try {
            startedObj.serverSocket.close();
        } catch (IOException e) {
            Log.w(TAG, "stopServer: 关闭异常?", e);//一般不会发生?
        }
    }

    public StartState getStartState() {
        return startedObj.startState;
    }

    /**
     * 客户端处理器
     */
    private class MyClientHandler implements BtChannelHandler {

        @Override
        public void onConnectResult(boolean success, BtChannel channel) {
            //代码做了功能固定,客户端认证失败后,不会调用server回调!
            serverConfig.serverCallback.onClientConnectChanged(true, channel);
        }

        @Override
        public void onChannelClosed(BtChannel channel) {
            //仅认证成功的客户端关闭时才会到达此处,认证失败的客户端无任何回调
            serverConfig.serverCallback.onClientConnectChanged(false, channel);
        }

        @Override
        public void onUnknownData(BtChannel channel, BaseBtDataPacket data) {
            serverConfig.serverCallback.onUnknownData(channel, data);
        }

        @Override
        public void exceptionCaught(BtChannel channel, Throwable e) {
            serverConfig.serverCallback.serverExceptionCaught(channel, e);
        }
    }

    /**
     * 服务端启动后的状态对象
     */
    private static class StartedObj {
        /**
         * 服务端启动状态
         */
        private StartState startState = StartState.STOPPED;
        private Map<Class<? extends BaseBtDataPacket>, DataMethodInvoker> dataHandlerMap;

        private ThreadPoolExecutor executor;
        private BluetoothServerSocket serverSocket;

        private void assertState(StartState state) {
            if (startState != state) throw new IllegalArgumentException("服务状态为:" + startState.desc);
        }

        private void buildExecutor(BtServerConfig serverConfig) {
            executor = serverConfig.executorSupplier.get();
        }

        private void releaseExecutor() {
            executor.shutdown();
            executor = null;
        }
    }

    /**
     * 服务端启动配置类;
     * 配置在下一次 {@link BluetoothServer#startServer()}时生效
     */
    public static class BtServerConfig implements BtDataHandlerRegister {

        private final BluetoothAdapter bluetoothAdapter;
        private final BtServerCallback serverCallback;
        private String serverName;
        private UUID uuid;
        /**
         * 登陆认证处理回调,null-没登陆认证功能
         */
        @Setter
        private BtLoginCallback loginCallback;
        /**
         * 心跳功能的回调,null-无心跳功能
         */
        @Setter
        private BtIdleCallback idleCallback;

        @Getter
        private ArrayList<BtDataHandler> lstDataHandler = new ArrayList<>();

        /**
         * 线程池策略,服务端发现线程和客户端消息接收线程共用一个线程池
         */
        @Setter
        private Supplier<ThreadPoolExecutor> executorSupplier = () -> new EfficientExecutor(
                2,//核心线程=服务端发现线程1+客户端线程1
                3,//最大线程=核心线程数+额外客户端1,BluetoothServerSocket应该是不能同时连多个客户端的??
                0,//没有等待队列,满员就抛RejectedExecutionException
                30, TimeUnit.SECONDS,
                "BtServer-",
                "蓝牙服务端");

        public BtServerConfig(BluetoothAdapter bluetoothAdapter, BtServerCallback serverCallback, String serverName, UUID uuid) {
            if (bluetoothAdapter == null) throw new IllegalArgumentException("bluetoothAdapter不能为空");
            if (serverCallback == null) throw new IllegalArgumentException("serverCallback不能为空");
            this.bluetoothAdapter = bluetoothAdapter;
            this.serverCallback = serverCallback;
            this.serverName = serverName;
            this.uuid = uuid;
        }
    }

    /**
     * 服务端启动状态
     */
    public enum StartState {
        STOPPED("已关闭"),
        STARTED("已启动"),
        STARTING("启动中");
        private final String desc;

        StartState(String desc) {
            this.desc = desc;
        }
    }

    /**
     * 服务端状态回调
     */
    public interface BtServerCallback {

        void onServerStartResult(boolean success);

        void onServerStopped();

        /**
         * 客户端上下线时的回调方法,可以在此处理SessionList
         *
         * @param onLine true-客户端连接(且认证)成功;false-客户端连接断开
         * @apiNote 注意:此处更新Session时,由于用户同时上下线时会有n个不同线程调用此方法,所以会有(List,Map)线程安全问题,所以应该加入synchronized;<br>
         * 并且服务器关闭时,建议在 {@link #onServerStopped()}中统一处理客户端下线的Session业务,而不是频繁地在此方法逐个处理session并通知ui
         */
        void onClientConnectChanged(boolean onLine, BtChannel client);

        /**
         * 异常处理方法
         *
         * @param client nullable;null-服务端的异常,notNull-客户端的异常
         */
        default void serverExceptionCaught(BtChannel client, Throwable e) {
            if (e instanceof IOException) {
                /*
                代码没bug时,一般是client程序非正常结束,没有正常关闭socket
                    导致IO异常:远程主机强迫关闭了一个现有的连接
                    此时onClientConnectChanged已经通知callback了,所以不需要打印
                注意:如果是其他问题,如代码bug引起的IO异常,则此处是一个大坑,会屏蔽异常信息,
                    但代码规范是,实现业务逻辑时,应自己捕获IO异常!
                 */
                return;
            }
            if (client != null) {
                Log.e(TAG, "serverExceptionCaught: " + client.getRemoteDevice().getName(), e);
            } else {
                Log.e(TAG, "serverExceptionCaught: ", e);
            }
        }

        /**
         * 收到客户端的未知消息
         */
        default void onUnknownData(BtChannel client, BaseBtDataPacket data) {
            Log.w(TAG, "onUnknownData: 未知数据=" + data);
        }
    }

}
